import {
  BaseCommand,
  CommandDialog,
  ComposableMenu,
  IAssemblyConstraint,
  IAssemblyDocTreeNode,
  ICmdDialogParams,
  useCommandManager,
} from '@amcax/base';
import { PickerAssembly, PickerMgr } from '@amcax/renderer';
import { ConstraintType } from '@app-cad-env/product/commands/cmd-constrain/type';
import {
  getAllConstraints,
  getDescendantKeys,
} from '@app-cad-env/product/utils';
import {
  editAssemblyConstraint,
  removeAssemblyConstraint,
} from '@app-cad-env/product/widgets/LeftPanel/ContextMenu/menuItems';
import { CommandFormFooter } from '@app-cad/common/components/command-form/footer';
import { PRODUCT_EVENT } from '@app-cad/common/Constants';
import { useProductCommandDisplay } from '@app-cad/common/hooks/useProductCommandDisplay';
import { DocumentContext } from '@app-cad/common/providers/documentProvider';
import { assemblyGetNodeTitleModified } from '@app-cad/common/utils/assembly-utils/AssemblyNodeTitleFuncs';
import { ctx2, userEvents } from '@app-cad/module';
import { subPartsOfRootChildren } from '@app-cad/pages/product-support/assembly-funcs';
import { allParts } from '@app-cad/pages/product-support/part-funcs';
import i18n from 'i18next';
import { MouseEvent, useContext, useEffect, useMemo, useState } from 'react';
import { contextMenu } from 'react-contexify';
import { ConstraintItem } from './ConstraintItem';
import { ReactComponent as NoConstraint } from './no-constraint.svg';

const viewConstraintsDialogId = 'view-constraints-dialog';
const constraintItemMenuId = 'constraint-item-menu';

function ViewConstraintsDialog({
  title,
  onClose,
  props,
}: ICmdDialogParams & {
  props: IAssemblyDocTreeNode | string[];
}) {
  const { docTree } = useContext(DocumentContext);
  const [popupNodeTitles, setPopupNodeTitles] = useState<Set<string>>(
    new Set(),
  );
  const [allConstraints, setAllConstraints] = useState(
    getAllConstraints(docTree) || [],
  );
  const { showObject, setObjectOpacity, hideObject, restoreObjectDisplay } =
    useProductCommandDisplay();

  useEffect(() => {
    const picker = PickerMgr.getPicker<PickerAssembly>('assembly');
    picker.clickNull(); // clear picked objects from the canvas

    const abortController = new AbortController();
    userEvents.on(
      PRODUCT_EVENT.ASSEMBLY_TREE_CHANGED,
      () => {
        setAllConstraints(getAllConstraints(docTree) || []);
      },
      abortController.signal,
    );

    document.addEventListener(
      'keydown',
      (event) => {
        if (event.key === 'Escape') {
          onClose();
        }
      },
      abortController,
    );

    return () => abortController.abort();
  }, []);

  const { fixedConstraints, myConstraints, dialogTitle } = useMemo(() => {
    const fixedConstraints = allConstraints
      .filter(({ originalObj: { type } }) => type === ConstraintType.Fix)
      .map(({ originalObj }) => originalObj as IAssemblyConstraint);

    const myConstraints = new Set<IAssemblyDocTreeNode>();
    const addMyConstraints = (node: IAssemblyDocTreeNode) => {
      allConstraints.forEach((constraintNode) => {
        const { item, target, type } =
          constraintNode.originalObj as IAssemblyConstraint;
        const descendantKeys = getDescendantKeys(node);
        const isCurrentNodeIncluded =
          item.includes(node.key) || target.includes(node.key);
        const isDescendantIncluded = descendantKeys.some(
          (key) => item.includes(key) || target.includes(key),
        );
        if (
          (isCurrentNodeIncluded || isDescendantIncluded) &&
          type !== ConstraintType.Fix
        ) {
          myConstraints.add(constraintNode);
        }
      });
    };
    let dialogTitle = title;
    if (Array.isArray(props)) {
      props.forEach((id) => {
        const findNodeRecursively = (
          nodes: IAssemblyDocTreeNode[] | undefined,
          id: string,
        ): IAssemblyDocTreeNode | undefined => {
          if (!nodes) {
            return undefined;
          }

          for (const node of nodes) {
            if (node.key === id) {
              return node;
            }
            const found = findNodeRecursively(node.children, id);
            if (found) {
              return found;
            }
          }

          return undefined;
        };
        const node = findNodeRecursively(docTree?.children, id);
        if (node) {
          const nodeTitle = assemblyGetNodeTitleModified(node);
          setPopupNodeTitles((prev) => {
            const newSet = new Set(prev);
            newSet.add(nodeTitle);
            return newSet;
          });

          addMyConstraints(node);
        }
      });
    } else {
      const nodeTitle = assemblyGetNodeTitleModified(props);
      setPopupNodeTitles(() => {
        const newSet = new Set<string>();
        newSet.add(nodeTitle);
        return newSet;
      });
      dialogTitle += ` （${nodeTitle}）`;
      addMyConstraints(props);
    }

    return {
      fixedConstraints,
      myConstraints: Array.from(myConstraints),
      dialogTitle,
    };
  }, [docTree, allConstraints, props]);

  useEffect(() => {
    if (myConstraints.length > 0) {
      const partKeys = new Set(
        myConstraints.flatMap((constraintNode) => {
          const { item, target } =
            constraintNode.originalObj as IAssemblyConstraint;
          return [item, target].map((s) => s.split(':')[0]);
        }),
      );
      const firstLevelPartKeys = new Set(
        docTree?.children?.map((node) => node.key) || [],
      );

      const involvedPartKeys = [];
      partKeys.forEach((partKey) => {
        if (firstLevelPartKeys.has(partKey)) {
          involvedPartKeys.push(partKey);
          return;
        }

        for (const value of subPartsOfRootChildren.values()) {
          if (value.has(partKey)) {
            involvedPartKeys.push(...value.values());
            break;
          }
        }
      });

      for (const key of allParts.keys()) {
        if (involvedPartKeys.includes(key)) {
          showObject(key);
          setObjectOpacity(key, 0.6);
        } else {
          hideObject(key);
        }
      }

      ctx2.viewer.sceneSetup.setToHomeView(400);
    }

    setSelectedIds([]);

    return restoreObjectDisplay;
  }, [myConstraints]);

  // Change selectedId to selectedIds (array)
  const [selectedIds, setSelectedIds] = useState<string[]>([]);

  function handleContextMenu(
    constraintNode: IAssemblyDocTreeNode,
    event: MouseEvent<HTMLSpanElement>,
  ) {
    setSelectedIds([constraintNode.key]); // context menu always single select

    const modalContent = document.querySelector(`#${viewConstraintsDialogId}`);
    const rect = modalContent?.getBoundingClientRect();
    const x = event.clientX - (rect?.left ?? 0);
    const y = event.clientY - (rect?.top ?? 0);

    contextMenu.show({
      id: constraintItemMenuId,
      event,
      props: constraintNode,
      position: { x, y },
    });
  }

  return (
    <CommandDialog
      id={viewConstraintsDialogId}
      title={dialogTitle}
      onClose={onClose}
      placement={'topLeft'}
    >
      {myConstraints.length > 0 ? (
        <div className='m-8'>
          {myConstraints.map((constraintNode) => (
            <div
              onContextMenu={(event: MouseEvent<HTMLSpanElement>) =>
                handleContextMenu(constraintNode, event)
              }
            >
              <ConstraintItem
                popupNodeTitles={popupNodeTitles}
                key={constraintNode.key}
                constraintNode={constraintNode}
                fixedConstraints={fixedConstraints}
                selectedIds={selectedIds}
                setSelectedIds={setSelectedIds}
              />
            </div>
          ))}
        </div>
      ) : (
        <div className='m-8 w-[320px] text-center'>
          <NoConstraint className='mx-auto my-4' />
          <div className='text-center text-gray-500 mt-2'>所选对象无配合</div>
        </div>
      )}
      <CommandFormFooter
        onClose={onClose}
        showSubmit={false}
        cancelText='关闭'
      />
      <ConstraintItemCMenu />
    </CommandDialog>
  );
}

export const ConstraintItemCMenu = () => {
  const { invokeCmd } = useCommandManager();

  return (
    <ComposableMenu
      id={constraintItemMenuId}
      menuItems={[editAssemblyConstraint, removeAssemblyConstraint]}
      environmentData={{ invokeCmd }}
    />
  );
};

export const cmdViewConstrains: BaseCommand = {
  id: 'CmdViewConstrains',
  label: 'command.view-constraints',
  icon: 'constraints/assembly-constraint',
  create: ((
    label: string,
    onClose: () => void,
    props: IAssemblyDocTreeNode,
  ) => (
    <ViewConstraintsDialog
      title={i18n.t(label)}
      onClose={onClose}
      props={props as any}
    />
  )) as any, // TODO: update BaseCommand type to support props of type `IAssemblyDocTreeNode | string[]`
};
